package gbench.sandbox.neo4j.draft;

import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.Map;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import gbench.common.tree.LittleTree.IRecord;
import gbench.commonApp.jdbc.Neo4jApp;
import gbench.commonApp.jdbc.Neo4jApp.Graph;
import gbench.commonApp.jdbc.Neo4jApp.Graph.VertexInfo;
import static gbench.common.tree.LittleTree.MFT;
import static gbench.common.tree.LittleTree.IRecord.FMT;
import static gbench.common.tree.LittleTree.IRecord.KVS2REC;
import static gbench.common.tree.LittleTree.IRecord.REC;
import static gbench.common.tree.LittleTree.KVPair.KVP;
import static gbench.common.tree.LittleTree.SimpleRecord.REC2;
import static gbench.common.tree.LittleTree.IRecord.CONS;
import static gbench.common.tree.LittleTree.IRecord.L;

/**
 * 我的工作流引擎 的：计算从1加到10000的累加和。
 * 与对MyWorkFlowApp做了可以使用常量做图边定义的增强。
 * @author gbench
 *
 */
public class MyWorkFlowApp2 {
    
    /**
     * 计算引擎
     * @author gbench
     *
     */
    class WorkFlowEngine{
        
        /**
         * 计算引擎:构造函数
         */
        public WorkFlowEngine(){
            this(null,null);
        }
        
        /**
         * 计算引擎
         * @param vlbl 顶点标签
         * @param elbl 边的标签
         */
        public WorkFlowEngine(final String vlbl,final String elbl){
            this.vlbl = vlbl==null?"NUM":vlbl;
            this.elbl = elbl==null?"OP":elbl;
            // 图结构信息的初始化的渲染控制函数
            neo4jApp.set_intialize_handler( g -> {// 图构架器的初始化
                g.setVertex_label_renderer(vtx->this.vlbl); // 顶点标号,注意这里需要加入 this 前缀，否则就引用了 参数中的数值了
                g.setEdge_label_renderer(edg->this.elbl);// 运标号,注意这里需要加入 this 前缀，否则就引用了 参数中的数值了
                g.setVertex_attrs_renderer(v->{
                   final var name = g.getVertex_name_renderer().apply(v);
                   final var attrset = g.getAttributeSet(name).filter( p->
                       !(p.value() instanceof Function)  // 去掉Function 属性
                       && !(p.value() instanceof Predicate)  // 去掉Predicate属性 
                   );// 获取节点的属性集合
                   
                   return Stream.of(// 属性结构的绘制
                        MFT("level:{0}",Graph.quote(v.key())),// 顶点阶层
                        MFT("name:{0}",Graph.quote(name)),// 顶点名称
                        MFT("{0}",attrset.kvs().stream()
                            .map(p->MFT("{0}:{1}",p.key(),Graph.quote(p.value()))).collect(Collectors.joining(","))
                        )// 顶点属性
                    ).filter(e->e.length()>0).collect(Collectors.joining(","));
                });
            }, false);// 初始化结构
        }
        
        /**
         * 绘图操作：把g 数据写入 neo4j数据库
         * @param g 图结构: 流程处理逻辑
         */
        public void plot(final Neo4jApp.Graph g) {
            final var edgeLbls = Stream.of(elbl)
                .filter(e->e!=null).collect(Collectors.joining("|")); // 提取所有的边的label
            Neo4jApp.getJdbc().withTransaction(sess->{// 查看买卖关系
                sess.sqlexecute(MFT("match (a)-[e:{0}]->(b) delete a,e,b",edgeLbls));//删除表
                sess.sqlexecute(MFT("create {0}",g));// 创建数据表
                final var sql = MFT("match (a)-[e:{0}]->(b) return a,e,b",edgeLbls);// 查询表
                System.out.println(sql);
                var mm = sess.sql2records(sql);
                System.out.println(FMT(mm));
                sess.sqlexecute(MFT("match p = (a)-[e:{0}]->(b) foreach ( n in nodes(p) | set n.marked=TRUE )",edgeLbls));// 更新顶点数据:设置标记
            });// withTransaction
        }
        
        /**
         * 使用session会话 开启g的处理流程
         * @param g 图形对象：流程处理逻辑
         * @param session 会话数据
         */
        public void execute(final Graph g, final IRecord session) {
            if(g==null || session == null) {
                System.err.println(" 图定义：流程逻辑g  与 会话数据源session 均不能为空 ");
                return;
            }
            run(g.getStarts(),session);
        }
        
        /**
         * 使用session会话 开启g的处理流程
         * @param g 图形对象：流程处理逻辑
         * @param activeVertices 激活顶点对象的名称序列，流程将以activeVertices为起点开始运行。
         * @param session 会话数据
         */
        public void execute(final Graph g, final List<String> activeVertices,final IRecord session) {
            if(g==null || session == null) {
                System.err.println(" 图定义：流程逻辑g，起始顶点序列activeVertices 与  会话数据源session 均不能为空 ");
                return;
            }
            run(activeVertices.stream().map(g::getVertexByName).collect(Collectors.toList()),session);
        }
        
        /**
         * 使用session会话 开启g的处理流程
         * @param g 图形对象：流程处理逻辑
         * @param session 会话数据
         */
        public void execute(final List<Graph.VertexInfo> activeVertices, final IRecord session) {
            if(activeVertices==null || session == null) {
                System.err.println(" 图定义：激活节点不能为空activeVertices  与会话数据源 session 均不能为空 ");
                return;
            }
            run(activeVertices,session);
        }
        
        /**
         * 从 活化顶点序列activeVertices来执行流程处理逻辑，初始数据与中间结果数据都位于session会话之中。
         * @param activeVertices 激活顶点序列
         * @param session 会话数据
         */
        public void run(final List<Graph.VertexInfo> activeVertices, final IRecord session) {
            if(activeVertices==null || session == null) {
                System.err.println(" 活化顶点序列activeVertices 与 会话数据源session 均不能为空 ");
                return;
            }
            
            if ( activeVertices.size() >0 ) {// 步骤执行:存在激活变量
                var avts = activeVertices;
                while(avts.size()>0) {
                    avts = avts.stream().collect(Collectors.groupingBy(VertexInfo::getNexts)).entrySet().stream() // 运算顶点数据流化,以方便使用函数化编程编程的管道式风格。
                        .filter(e->e.getKey().size()>0) // 确保存在下一步的计算顶点：即至少为1.这是判断运算结束条件的关键。比如E的下一步计算顶点就不存在，即程序运行到E就计算结束了。
                        .parallel() // 开启并行计算
                        .map( call -> {// 方法调用解析:call是一个完整的方法调用 call.getKey()是方法对象。 call.getValue()是方法调用的形式参数列表。实际菜蔬需要再会话数据给予寻找。
                            final var methods = call.getKey(); // 方法调用的方法对象
                            //final var formal_args = call.getValue(); // 形式参数 即形参
                            final var actual_args = REC();// 实际参数即实参
                            final var method = methods.get(0);// 提取操作对象:运算顶点
                            final var fx = method.getProperty("fx"); // 方法名称,即运算处理器的指令名称, fx 表示  f(x) 即函数的的意思。
                            final var constants = method.attr2("constants",REC()).kvstream() // 常量是位于constants属性之中的。常量的名就是 "value",常量的位置参数就是 "key"
                                .map(e->REC("symbol",e.get("value"), "pos",e.get("key")));// 提取路径中的常量
                            final var variables = method.getInEdges().stream() // 变量是保存在边中的。边的name 就是 变量的名的，边的 属性pos 就是位置参数
                                .map(e->REC("symbol",e.startNode().getName(), "pos",e.attr("pos",0)));// 提取路径中得变量
                            
                            // 数据的流化处理
                            Stream.of(constants,variables).flatMap(e->e)// 把常量和变量混合在一起 来进行拼装:结果就是 一个 (symbol,pos) 的record 序列。
                            //对边按照pos参数进行排序。以保证对实参的添加可以保证actual_args可以按照method的设计的参数顺序进行填充/追加
                            .sorted((e1,e2)-> Stream.of(e1,e2).map(e->e.i4("pos",0)).reduce((a,b)->a-b).get()) // 参数顺序重排
                            .forEach( edge -> { // 输入进来的边:用来表示参数实装如方法调用的关系。
                                final var symbol = edge.str("symbol");// 输入边上的变量符号
                                final var value = symbol.matches(CONSTANT_PATTERN) // 符号检测 是数值常量 还是 符号变量，数值常量 直接取值。符号变量 session 取值。
                                    ? Double.parseDouble(symbol.replaceAll("\\s+","")) // 数值常量:去除多余的空格
                                    : session.get(symbol); // 符号变量
                                actual_args.add(symbol,value);// 从会话数据中读取参数值
                            });// method.getInEdges().forEach
                            
                            //System.out.println(actual_args);
                            if ( actual_args.values().stream().filter(e->e==null).count()>0 ) {// 含有未解析变量
                                //System.out.println("函数参数不足不予激活顶点【"+call+"】"+output_symbol+": "+fx+" ("+actual_args+")\n"); // 存在没有解析的变量，不予进行数据计算
                                return null;
                            } else { // actual_args 配置完备,可以完成计算。
                                final var result = processor.eval(fx, actual_args.values());// 方法计算的返回值。
                                final var vtx = method.jumpTo(v->result2vertex(result, session, v)); // 跳转到output_symbol的目标顶点。将计算过程推入下一个 阶段。
                                return vtx;// 把vtx 推送到 activeVertices
                            }// actual_args 配置完毕
                        }).filter(e->e!=null).collect(Collectors.toList()); // 提取出非空的顶点 作为 新一轮的 activeVertices，进行循环给与递归调用。
                }  // while(aa.size()>0) 
            }// if ( activeVertices.size() >0 ) {// 步骤执行:存在激活变量
        }
        
        /**
         * 对返回结果result 进行解释:以寻找下一个计算节点
         * @param result 返回结果
         * @param session 会话缓存
         * @param output_symbol 输出符号
         * @param method 方法顶点
         * @return 根据输出符号与执行结果
         */
        @SuppressWarnings("unchecked")
        public VertexInfo result2vertex(final Object result,final IRecord session, final VertexInfo method) {
            final var attrs = method.getAttributes();// 提取方法属性数据
            final var mode = attrs.str("mode","operation").toLowerCase();// 提取方法的类型mode: 默认为operation
            final var output_symbol = method.getName();// 输出变量符号(method名称就是输出变量的符号)即运算结果的变量符号:输出符号,亦即中间结果变量的符号。 用于把计算过程推进到下一运算阶段。
            if(mode.startsWith("if")){// if 指令是一个跳转指令：结果返回标号
                session.add(output_symbol,result);// 把计算结果存入会话数据。
                final Function<Object,Function<IRecord,String>> f = (o)->{
                    if (o instanceof String){// 字符串类型表示顶点名称：即分支标号
                        return rec->o.toString();
                    } else if (o instanceof Function){ // 函数类型表示 回调函数：可以通过执行返回 分支标号
                        return (Function<IRecord,String>) o;
                    } else {// 其他类型表示非法值，返回方法名称所制定的 顶点标号。作为默认值。
                        return rec->method.getName();
                    }//if
                };// Function 属性读取函数
                final var test_pred = (Predicate<IRecord>)attrs.get( "test",
                    (Predicate<IRecord>) (rec)->(Boolean)result);// test 条件检测 函数
                final var then_func = attrs.get("then",f);// then 真值分支
                final var else_func = attrs.get("else",f);// else 假值分支
                final var b = test_pred == null
                    ? (Boolean)result 
                    : test_pred.test(session);// 返回的结果值
                return method.getVertexByName( b ? then_func.apply(session) : else_func.apply(session) );
            } else {// 一般的返回值指令
                final var updates = attrs.filter( kvp->kvp.key().startsWith("set") ).values() // 提取设置对象
                    .stream().filter(e->e!=null)
                    .map( ee->ee.getClass().isArray()
                        ? ( String[] ) ee // 提取数组元素
                        : ( ee instanceof Collection 
                            ? ( Collection<?> ) ee // 集合对象
                            : ( ee instanceof Map ? ( (Map<?,?>)ee ).values() : Arrays.asList(ee) ) // Map 对象
                         ).stream().toArray(String[]::new) )// 把元素转换成数组类型
                    .reduce(IRecord::A).orElse(new String[] {});// 提取updates系列变量
                
                // 把计算结果存入会话数据。
                Stream.of(CONS(output_symbol,updates)).distinct().forEach(symbol->session.add(symbol,result));// 会话变量生成与保存
                final var callback = ( Function<IRecord,Object> )attrs
                    .filter(kvp->kvp.key().startsWith("callback"))
                    .get(0);// 回调函数
                
                if(callback!=null) callback.apply(session);
                return method.getVertexByName(output_symbol);
            }//if
        }

        /**
         * 生成一个图结构
         * @param edgeLineInfos 边结构信息
         * @return Graph 图结构
         */
        public Graph getGraph(final IRecord edgeLineInfos) {
            //System.out.println("输入边定义");
            //edgeLineInfos.foreach((path,defination)->System.out.println(path+"--->"+defination));// edgeLineInfos
            final var g = neo4jApp.graph(edgeLineInfos);
            return g;
        }
        
        /**
         * 将流程定义文件解析成 计算图结构 edgeLineInfos
         * @param definations 图定义：{(参数检点序列：参数顶点1,参数顶点2) ---> {处理顶点属性集合:method 顶点}}
         * @return 计算图的边结构 edgeLineInfos
         */
        public IRecord parse(final IRecord definations) {
           return KVS2REC(definations.applyForKvs( IRecord::STRING2REC, e->(IRecord)e ) // 转换成一个 IRecord,IRecord的数据结构
            .entrySet().stream().flatMap( e-> { // 谓词动作拆解
                final var part1 = e.getKey(); // 开始顶点
                final var part2 = e.getValue(); // 结束顶点
                if( part1.size()<2 ) { // 单顶点
                    return Stream.of( KVP (
                        MFT( "{0}",part1.get(0)), // 单顶点边的顶点名称
                        REC(part2.aoks(k->"$"+k)) // 单顶点边的属性:增$前缀表示这是一个顶点属性。
                    )); // 为顶点追加属性:把边的属性拷贝到单顶点属性上去
                } else { // 双顶点
                    final var params_part = part1; // 参数部分：即参数列表，开始顶点：操作数
                    final var method_part = part2; // 方法部分，即运算方法，结束顶点:操作子
                    final var end_symbol = method_part.get("name") ;// 端点符号，运算节点的符号。
                    final var method_name = method_part.get("method");// 方法名称：蕴含于method_part中的运算方法。
                    final var constants = params_part.filter( param->param.value().toString().matches(CONSTANT_PATTERN) ); // 提取常量表达式
                    
                    return params_part.kvstream()// 提取参数列表中的各个分量
                        .filter( param-> !param.str("value").matches(CONSTANT_PATTERN) ) // 去除掉常量数值
                        .map( param -> KVP( // 生成一个KVPair(key:顶点序号 ,value:顶点符号)
                            // fx的方法参数
                            MFT( "{0}/{1}", param.get("value"), end_symbol ) // 双顶点边的 路径表达
                            
                            // fx的返回值结果
                            ,part2.derive( REC( // 自定义参数列表
                                 "pos" , MFT( "{0}", param.get("key") )// 参数位置,注意pos参数一定腰围数值类型，以保证流程解析过程可以通过pos来获取正确参数位置。
                                 ,"name" , MFT( "{0}->{1}", param.get("value"), end_symbol ) // 边的名称路径
                                 ,"method" , method_name // 方法名称，起点与端点都写入
                                 ,"1#fx" , method_name // 代表fx: 调用方法的名称。写入端点的
                                 ,"1#constants" ,constants // 常量集合
                            ) ) // 构造结果顶点的结构:结果顶点可能是中间顶点也可以是最终中顶点。
                        ));// 把startNode 和  endNode 拼接成 双顶点的边。
                }// part1->part2的边转换。
            }));// 计算图的图表结构。
        }
        
        /**
         * 图形渲染
         * @param defination 图定义：{输入参数顶点序列->处理方法订单的定义}
         * @return Graph 图结构
         */
        public Graph render(final IRecord definations) {
            return getGraph(parse(definations));
        }
        
        /**
         * 添加执行程序
         * @param subroutines 子程序
         */
        public void addSubroutines(final IRecord subroutines) {
            processor.add(subroutines);
        }
        
        final Neo4jApp neo4jApp = Neo4jApp.newInstance("",null,null); // 创建图的应用App，省略节点前缀
        final String vlbl;// 顶点的label
        final String elbl;// 边的label
        final IRecord processor = REC(// 指令解析/处理器
            "add",  (Function<List<Object>,Double>)(ll->ll.stream().map(v->IRecord.obj2target(v,Double.class)).reduce((a,b)->a+b).get()), // 加法运算
            "minus",(Function<List<Object>,Double>)(ll->ll.stream().map(v->IRecord.obj2target(v,Double.class)).reduce((a,b)->a-b).get()), // 减法运算
            "mul",  (Function<List<Object>,Double>)(ll->ll.stream().map(v->IRecord.obj2target(v,Double.class)).reduce((a,b)->a*b).get()), // 乘法运算
            "div",  (Function<List<Object>,Double>)(ll->ll.stream().map(v->IRecord.obj2target(v,Double.class)).reduce((a,b)->a/b).get()) // 除法运算
        );// 指令处理器
        
        final String CONSTANT_PATTERN = "\\s*[+-]?\\s*(\\d)\\s*.?\\s*([\\d]*)\\s*";// 常量模式:目前只支持数字，并且会把识别的出来的字符串中的 空格全部给去除掉
        
    }// 类 ComputeEngine 图结构
    
    // 运算指令列表，所谓运算指令就是可以绑定到图的顶点上的运算程序。只要为addProto 衍化/赋予（derive）一个顶点名称 name属性：
    // 比如 addProto.derive("name","A") 就创建见了一个拥有addProto运算能力的图顶点。
    final IRecord addProto = REC( "method","add", "1#description","加法"); // 加法运算指令模版
    final IRecord minusProto = REC( "method","minus", "1#description","减法"); // 减法运算指令模版
    final IRecord mulProto = REC( "method","mul", "1#description","乘法"); // 乘法运算指令模版
    final IRecord divProto = REC( "method","div", "1#description","除法"); // 除法运算指令模版
    
    // 流程控制指令
    final IRecord eqProto = REC( "method","eq", "1#description","等于"); // 相等判断指令模版
    final IRecord ltProto = REC( "method","lt", "1#description","小于"); // 小于判断指令模版
    final IRecord gtProto = REC( "method","gt", "1#description","大于语句"); // 大于判断指令模版
    
    //数据访问运指令
    final IRecord carProto = REC( "method","car", "1#description","赋值语句"); // 提取首项元素指令模版
    
    @Test
    public void foo() {
       
        final var definations = REC2(// 计算图的结构，这里采用 小写字符表达 简单顶点，大写字符表示 高阶顶点即边
                
            // 流程定义的 格式：标号变量Lx,参数变量 x,比如：L1,one, 表示这是一条入口点为L1的 执行语句分支。
            // 表达式的项目:L1 是入口参数,注意 proto中的回调函数需要指明参数位置 即 1#udate,1#callback,以使得回调函数只绑定到参数顶点。而不是绑定两个顶点。
            // 这样就可以避免后面的顶点前面的顶点的属性进行覆盖。比如 对于 L1/L2,L2的callback 就会覆盖掉L1的callback,所以我们在L2的定义中制定1@callback,以保证callback函数绑定到L3上。
            
            //L1 分支入口点 
            "L1,1.0 ",  addProto.derive( "name", "L2", 
                                "1#title", "程序入口",
                                "1#sets","L1", // L1同步更新
                                "1#callback", (Function<IRecord,Object>)sess->{
                                    System.out.println("update L1:"+sess.get("L1"));
                                    return null;
                                }) // 加法运算
            //L2 分支入口点
            ,"L2,1.0",  carProto.derive( "name","L3", 
                                "1#title","计数增加",
                                "1#callback",(Function<IRecord,Object>)sess->{// 真值分支,一定要写成1@callback,保证L3不覆盖L2的callback
                                    
                                    final var num = sess.lng("L1"); // 改用长整形，防止数值溢出。
                                    sess.compute("sum",(Function<Long,Long>)v->v==null?num:v+num);
//                                    final var sum = sess.get("sum");
//                                    final var sum1 = Stream.iterate(1,i->i<=num,i->i+1).collect(Collectors.summarizingInt(o->o)).getSum(); // 标准值
//                                    System.out.println(MFT("sum:{0}, s1:{1},res:{2}",sum,sum1,(sum+"").equals(sum1+"")));
                                    
                                    return "L3";
                                }) // 路由运算
            //L3 分支入口点
            ,"L3,+1.000",  carProto.derive( "name","L4", 
                            "1#title","选择判断",
                            "1#mode","if", // mode 指明 指令类型,if 表示分支跳转 ,根据 test中的 标号 跳转到指定 分支入口。如果补填写默认为"operation"
                            "1#test",(Predicate<IRecord>)sess->sess.i4("L1") < MAX_TIMES, // 循环检测条件
                            "1#then","L1"// True 分支
                            //,"1#else",(Function<IRecord,String>)sess->{ System.out.println("退出循环,跳转到L5");sess.set("L5", 1);/*设置入口值*/return"L5";}
                        ) // 跳转运算
            
            //L4 分支入口点
            ,"L4,1.0",  carProto.derive("name","L5","1#callback",(Function<IRecord,String>)sess->{
                System.out.println("进入到L5流程顶点！");
                return null;
            }) // 终止分支
            
            //L5 分支入口点
            ,"L5,1.0",  carProto.derive("name","E","1#callback",(Function<IRecord,String>)sess->{
                final var g = sess.get("$g",Graph.class);
                final var endVertex  = g.getVertexByName("E");// 终止顶点
                System.out.println(MFT("进入到E，将程序结束顶点!\n{0}",endVertex.getProperty("comment")));
                
                return null;
            }) // 终止分支
            
            //程序 运行终止点
            ,"E",   REC("comment","程序终止终止顶点的comment。")
            
        );// definations
        
        final var engine = new WorkFlowEngine();// 构造流程引擎
        engine.addSubroutines(REC(
            "eq",   (Function<List<Object>,Object>)(ll->ll.get(0).equals(ll.get(1))), // 相等运算
            "lt",   (Function<List<Object>,Object>)(ll->ll.stream().map(v->IRecord.obj2target(v,Double.class)).reduce((a,b)->a-b).get()<0), // 小于运算
            "gt",   (Function<List<Object>,Object>)(ll->ll.stream().map(v->IRecord.obj2target(v,Double.class)).reduce((a,b)->a-b).get()>0), // 大于运算
            "car",  (Function<List<Object>,Object>)(ll->ll.get(0)) // 提取参数首个元素作为返回值。
        ));// 装载自定义流程函数
        final var g = engine.render(definations);
        final var session_data = REC("L1",0,"cnt",100,"$g",g); // 会话数据，会话数据中的符号，即运算过程中的变量铭恒，所以会话数据是一个变量池。是计算的过程记忆(memory),$g图结构变量
        //engine.execute(g, session_data); // 启动程序,流程以入口点启动流程
        engine.execute(g,L("L1"), session_data); // 指定流程入口点启动流程。
        
        System.out.println( MFT("\n计算结果为:{0},正确值为:{1}",
            session_data.get("sum"),// 流程计算结果
            Stream.iterate(1,i->i<=MAX_TIMES,i->i+1).collect(Collectors.summarizingInt(e->e)).getSum()) // 库函数计算结果
        );// println
        System.out.println(MFT("会话中的数据状态为:\n{0}",session_data.filter(kvp->!kvp.key().equals("$g")).toString2()));// 输出会话数据，$g图结构对象的变量名
        
        //流程渲染
        engine.plot(g);
        
        // 打印图结构数据
        System.out.println(g);
    }
    
    @Test
    public void bar() {
        var sum = 0d;
        for(int i=1; i<= MAX_TIMES; i++) {
            sum+=i;
            System.out.println(MFT("sum:{0}",sum));
        }//for
        System.out.println(MFT("result:{0}",sum));
    }
    
    public static int MAX_TIMES = 10000;// 最大的执行次数
}
